#include "StdAfx.h"
#include "D3DCameraImpl.h"
#include "RenderableObject.h"
#include "SkyBox.h"


///////////////////////////////////////////////////////////////////////////////

CD3DCameraImpl::CD3DCameraImpl(IDirect3DDevice9& d3Device)
      : m_d3Device(d3Device),
      m_fov(60),
      m_nearZPlane(1.01f),
      m_farZPlane(5000.0f),
      m_viewFrustrum(m_mtxView, m_mtx3DProjection),
      m_skyBox(NULL),
      m_mtxViewDirty(true),
      m_mtxProjectionDirty(true)
{
   m_viewport.X = 0;
   m_viewport.Y = 0;
   m_viewport.Width = 800;
   m_viewport.Height = 600;
   m_viewport.MinZ = 0.0f;
   m_viewport.MaxZ = 1.0f;

   m_rightVec = D3DXVECTOR3(1, 0, 0);
   m_upVec    = D3DXVECTOR3(0, 1, 0);
   m_lookVec  = D3DXVECTOR3(0, 0, 1);
   m_position = D3DXVECTOR3(0, 0, 0);

   D3DXMatrixIdentity(&m_mtxIdentity);
   D3DXMatrixIdentity(&m_mtxView);
   D3DXMatrixIdentity(&m_mtx2DProjection);
   D3DXMatrixIdentity(&m_mtx3DProjection);
}

///////////////////////////////////////////////////////////////////////////////

CD3DCameraImpl::~CD3DCameraImpl(void)
{
   m_skyBox = NULL;
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::setViewport(UINT x, UINT y, UINT width, UINT height, float nearZPlane, float farZPlane)
{
   m_viewport.X = 0;
   m_viewport.Y = 0;
   m_viewport.Width = width;
   m_viewport.Height = height;
   m_viewport.MinZ = 0.0f;
   m_viewport.MaxZ = 1.0f;
   m_nearZPlane = nearZPlane;
   m_farZPlane = farZPlane;

   m_mtxProjectionDirty = true;
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::render(const std::vector<IRenderableObject*>& renderableObjects)
{
   updateViewMtx();
   updateProjectionMtx();

   m_d3Device.Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFF007FFF, 1.0f, 0);

   m_d3Device.SetViewport(&m_viewport);

   m_d3Device.BeginScene();

   if (m_skyBox != NULL)
   {
      m_d3Device.SetTransform(D3DTS_VIEW, &m_mtxView);
      m_d3Device.SetTransform(D3DTS_PROJECTION, &m_mtx3DProjection);
      m_d3Device.SetRenderState(D3DRS_LIGHTING, FALSE);
      m_d3Device.SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
      m_skyBox->render(*this);
      m_d3Device.SetRenderState(D3DRS_LIGHTING, TRUE);
      m_d3Device.SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
   }

   render3DElements(renderableObjects);

   m_d3Device.EndScene();
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::render3DElements(const std::vector<IRenderableObject*>& renderableObjects)
{
   for (unsigned int i = 0; i < renderableObjects.size(); i++)
   {
      if (renderableObjects[i]->is3D())
      {
         m_d3Device.SetTransform(D3DTS_VIEW, &m_mtxView);
         m_d3Device.SetTransform(D3DTS_PROJECTION, &m_mtx3DProjection);
      }
      else
      {
         m_d3Device.SetTransform(D3DTS_VIEW, &m_mtxIdentity);
         m_d3Device.SetTransform(D3DTS_PROJECTION, &m_mtx2DProjection);
      }
      renderableObjects[i]->render(m_viewFrustrum, m_d3Device);
   }
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::setSkyBox(CSkyBox& skyBox)
{
   m_skyBox = &skyBox;
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::updateViewMtx()
{
   if (m_mtxViewDirty == false) return;

   // regenerate vectors
   D3DXVec3Normalize(&m_lookVec, &m_lookVec);
   D3DXVec3Cross(&m_upVec, &m_lookVec, &m_rightVec);
   D3DXVec3Normalize(&m_upVec, &m_upVec);
   D3DXVec3Cross(&m_rightVec, &m_upVec, &m_lookVec);
   D3DXVec3Normalize(&m_rightVec, &m_rightVec);

   // create a view matrix
   D3DXMatrixIdentity(&m_mtxView);
   m_mtxView._11 = m_rightVec.x; m_mtxView._12 = m_upVec.x; m_mtxView._13 = m_lookVec.x; 
   m_mtxView._21 = m_rightVec.y; m_mtxView._22 = m_upVec.y; m_mtxView._23 = m_lookVec.y;
   m_mtxView._31 = m_rightVec.z; m_mtxView._32 = m_upVec.z; m_mtxView._33 = m_lookVec.z;
   m_mtxView._41 = -D3DXVec3Dot(&m_position, &m_rightVec);
   m_mtxView._42 = -D3DXVec3Dot(&m_position, &m_upVec);
   m_mtxView._43 = -D3DXVec3Dot(&m_position, &m_lookVec);

   m_mtxViewDirty = false;
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::updateProjectionMtx()
{
   if (m_mtxProjectionDirty == false) return;

   
   D3DXMatrixIdentity(&m_mtx3DProjection);
   D3DXMatrixIdentity(&m_mtx2DProjection);
   float aspect = (float)m_viewport.Width / (float)m_viewport.Height;

   D3DXMatrixPerspectiveFovLH(&m_mtx3DProjection, D3DXToRadian(m_fov), aspect, m_nearZPlane, m_farZPlane);
   D3DXMatrixOrthoOffCenterLH(&m_mtx2DProjection, 0, (float)m_viewport.Width, (float)m_viewport.Height, 0, 0.0f, 1.0f);

   m_mtxProjectionDirty = false;
}

///////////////////////////////////////////////////////////////////////////////

void CD3DCameraImpl::lookAt(const D3DXVECTOR3& lookAtPt) 
{
   D3DXMATRIX lookAtMtx;
   D3DXVECTOR3 upVec(0, 1, 0);
   D3DXMatrixLookAtLH(&lookAtMtx, &m_position, &lookAtPt, &upVec);

   m_rightVec = D3DXVECTOR3(lookAtMtx._11, lookAtMtx._21, lookAtMtx._31);
   m_upVec    = D3DXVECTOR3(lookAtMtx._12, lookAtMtx._22, lookAtMtx._32);
   m_lookVec  = D3DXVECTOR3(lookAtMtx._13, lookAtMtx._23, lookAtMtx._33);

   m_mtxViewDirty = true;
}

///////////////////////////////////////////////////////////////////////////////
